﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;

namespace Lucene.Linq.Expressions
{
    /// <summary>
    /// Second-pass expression tree visitor. 
    /// Used for converting an expression tree into a final representation
    /// </summary>
    internal class LuceneExpressionVisitor : ExpressionVisitor
    {
        protected override Expression Visit(Expression exp)
        {
            if (exp == null)
            {
                return null;
            }

            switch ((LuceneExpressionType) exp.NodeType)
            {
                case LuceneExpressionType.Index:
                    return VisitIndex((IndexExpression) exp);
                case LuceneExpressionType.Field:
                    return VisitField((FieldExpression) exp);
                case LuceneExpressionType.Select:
                    return VisitSelect((SelectExpression) exp);
                case LuceneExpressionType.Projection:
                    return VisitProjection((ProjectionExpression) exp);
                    //case LuceneExpressionType.OrderBy:
                    //    return VisitOrderBy((OrderByExpression)exp);
                default:
                    return base.Visit(exp);
            }
        }

        protected virtual Expression VisitIndex(IndexExpression index)
        {
            return index;
        }

        protected virtual Expression VisitField(FieldExpression field)
        {
            return field;
        }

        protected virtual Expression VisitSelect(SelectExpression select)
        {
            Expression from = VisitSource(select.From);
            Expression where = Visit(select.Where);
            ReadOnlyCollection<FieldDeclaration> fields = VisitFieldDeclarations(select.Fields);

            if (from != select.From || where != select.Where || fields != select.Fields)
            {
                return new SelectExpression(select.Type, select.Alias, fields, select.DefaultFieldNames, from, where);
            }

            return select;
        }

        protected virtual Expression VisitSource(Expression source)
        {
            return Visit(source);
        }

        protected virtual Expression VisitOrderBy(OrderByExpression orderByExpression)
        {
            return Visit(orderByExpression);
        }

        protected virtual Expression VisitProjection(ProjectionExpression proj)
        {
            var source = (SelectExpression) Visit(proj.Source);
            Expression projector = Visit(proj.Projector);

            if (source != proj.Source || projector != proj.Projector)
            {
                return new ProjectionExpression(source, projector);
            }

            return proj;
        }

        protected ReadOnlyCollection<FieldDeclaration> VisitFieldDeclarations(
            ReadOnlyCollection<FieldDeclaration> fields)
        {
            List<FieldDeclaration> alternate = null;

            for (int i = 0, n = fields.Count; i < n; i++)
            {
                FieldDeclaration field = fields[i];
                Expression e = Visit(field.Expression);

                if (alternate == null && e != field.Expression)
                {
                    alternate = fields.Take(i).ToList();
                }

                if (alternate != null)
                {
                    alternate.Add(new FieldDeclaration(field.Name, e));
                }
            }

            if (alternate != null)
            {
                return alternate.AsReadOnly();
            }

            return fields;
        }
    }
}